/*
 * Funambol is a mobile platform developed by Funambol, Inc. 
 * Copyright (C) 2008 Funambol, Inc.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission 
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE 
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Affero General Public License 
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 * 
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite 
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 * 
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 * 
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably 
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */
#include "FunambolAPIWrapperImpl.h"

#include "base/fscapi.h"
#include "event/SyncItemListener.h"
#include "event/SyncSourceListener.h"
#include "base/Log.h"
#include "base/util/utils.h"
#include "UIController.h"
#include "iPhoneListeners.h"
#ifndef FUNAMBOL_APP_STORE
#include "NoteDataStore.h"
#include "NoteDataConverter.h"
#include "NoteChangesTracker.h"
#include "EventDataStore.h"
#include "EventDataConverter.h"
#include "EventChangesTracker.h"
#endif
#include "stringUtils.h"
#include "customs.h"

#define EVENTDBPATH "/private/var/mobile/Library/Calendar/Calendar.sqlitedb"
#define NOTEDBPATH "/private/var/mobile/Library/Notes/notes.db"


#import <UIKit/UIKit.h>


FunambolAPIWrapperImpl* FunambolAPIWrapperImpl::apiWrapperImpl = NULL;

FunambolAPIWrapperImpl* FunambolAPIWrapperImpl::getInstance(){
    if (apiWrapperImpl == nil) {
        apiWrapperImpl = new FunambolAPIWrapperImpl();
    }
    
    return apiWrapperImpl;
}

void FunambolAPIWrapperImpl::loadDefaultConfig()
{
    //clientConfig = new DMTClientConfig(server.c_str());
    clientConfig->setAccessConfig(* iPhoneDefaultConfigFactory::getAccessConfig());
    clientConfig->setDeviceConfig(* iPhoneDefaultConfigFactory::getDeviceConfig());
    
    clientConfig->setSyncSourceConfig(* iPhoneDefaultConfigFactory::getSyncSourceConfig("addressbook"));
#ifndef FUNAMBOL_APP_STORE
    clientConfig->setSyncSourceConfig(* iPhoneDefaultConfigFactory::getSyncSourceConfig("calendar"));
    clientConfig->setSyncSourceConfig(* iPhoneDefaultConfigFactory::getSyncSourceConfig("note"));
#endif
    clientConfig->save();
    
}



bool FunambolAPIWrapperImpl::loadClientConfig(bool force) {
    
    if (clientConfig == NULL || force) {
        delete clientConfig; clientConfig=NULL;
        delete abss; abss=NULL;
        
        clientConfig = new DMTClientConfig(server.c_str());
        
        if (!clientConfig->read()) {
            LOG.error("[SyncThread] Unable to open configuration %s", server.c_str());
            return false;
        }
       
        LOG.info(clientConfig->getAccessConfig().getUsername());
    }else{
        iPhoneDefaultConfigFactory::upgrade(clientConfig);
    }
    LOG.setLevel(clientConfig->getDeviceConfig().getLogLevel());
    return true;
}


void FunambolAPIWrapperImpl::initializeClient(){
    
    // Check for recent upgrade
    
    clientConfig = new DMTClientConfig(server.c_str());
    
    UIDevice* UUID = [[UIDevice alloc] init];
    StringBuffer deviceId;
    if([UUID.model isEqual:@"iPhone"]){
        deviceId = "iph-";
    }else if([UUID.model isEqual:@"iPod"]){
        deviceId = "ipt-";
    }else{
        deviceId = "iph-";
    }
    char base64      [64];
    char digest      [16];
    memset(digest,      0, 16);
    memset(base64,      0, 64);
    
    calculateMD5([UUID.uniqueIdentifier UTF8String], strlen([UUID.uniqueIdentifier UTF8String]), digest);
    b64_encode((char*)base64, digest, 16);
    
    deviceId.append(base64);
    
    [UUID release];
    
    if (!clientConfig->read() ||  !clientConfig->open() ||
        strcmp(clientConfig->getDeviceConfig().getDevID(), deviceId.c_str()) != 0){
        //Load the default configuration
        LOG.info("loading default configuration");
        loadDefaultConfig();
        
        // Initialize Tracking Database
        if (AddressBookSyncSource::initializeDatabase()){
            LOG.error("AddressBookSyncSource::initializeDatabase failed - %s", getLastErrorMsg());
            //TODO disable Contacts
        }
#ifndef FUNAMBOL_APP_STORE
        int val = NoteDataStore::checkDBFunTable(NOTEDBPATH);
        if(val!=0){
            LOG.error("NoteDataStore::checkDBFunTable(NOTEDBPATH) failed - %i", val);
        }
        
#endif
        
    } else {
        // Load config from properties
        loadClientConfig();
        
        // Check validity of tracking DBs
        if (AddressBookSyncSource::verifyDatabase()){
            LOG.error("AddressBookSyncSource::verifyDatabase failed - %s", getLastErrorMsg());
            //TODO disable Contacts
        }
#ifndef FUNAMBOL_APP_STORE
            
        int val = NoteDataStore::checkDBFunTable(NOTEDBPATH);
        if(val!=0){
            LOG.error("NoteDataStore::checkDBFunTable(NOTEDBPATH) failed - %i", val);
        }
        // TODO: Check to see if we must sync immediately
        
#endif
    }
    
    LOG.info("Initialization complete");
}

FunambolAPIWrapperImpl::~FunambolAPIWrapperImpl(){
    delete clientConfig;
}

FunambolAPIWrapperImpl::FunambolAPIWrapperImpl(){
    clientConfig = NULL;
    server = "funambol/";
    contactIsSyncing = false;
    calendarIsSyncing = false;
    noteIsSyncing = false;
    abss = NULL;
#ifndef FUNAMBOL_APP_STORE

    nss = NULL;
    ess = NULL;
#endif
    recoverDirection = SYNC_REFRESH_FROM_SERVER; //Default
    initializeClient();
}


void FunambolAPIWrapperImpl::stopSyncContacts(){
    if(contactIsSyncing){
        getAddressBookSyncSource()->stopSync();
    }else{
        //Do nothing for now
    }
}

void FunambolAPIWrapperImpl::stopSyncAndExit(){
    stopSyncContacts();
    exit(0);
}

int FunambolAPIWrapperImpl::cleanup(const char* syncsourcename) {
    //if (!loadClientConfig(true)) {
    if (!loadClientConfig()) {
        return 0;
    }
    
    return this->doCleanup(syncsourcename);
}

int FunambolAPIWrapperImpl::doCleanup(const char* syncsourcename) {
    if (!loadClientConfig()) {
        return 0;
    }
    if (strcmp(syncsourcename, "addressbook") == 0 ){
        int ret = getAddressBookSyncSource()->removeAllItems();
        clientConfig->getSyncSourceConfig("addressbook", TRUE)->setLast(0);
        clientConfig->save();
        return ret;
    }
#ifndef FUNAMBOL_APP_STORE
    else if (strcmp(syncsourcename, "calendar") == 0){
        int ret = getEventSyncSource()->removeAllItems();
        clientConfig->getSyncSourceConfig("calendar", TRUE)->setLast(0);
        clientConfig->save();
        return ret;
    }else if (strcmp(syncsourcename, "note") == 0 ){
        int ret = getNoteSyncSource()->removeAllItems();
        clientConfig->getSyncSourceConfig("note", TRUE)->setLast(0);
        clientConfig->save();
        return ret;
    }
#endif
    
    return 0;
}



void FunambolAPIWrapperImpl::setRecoverMode(SyncMode mode)
{
    recoverDirection = mode;
}

int FunambolAPIWrapperImpl::recoverContacts(id listener) {
    if (!loadClientConfig(true)) {
        return 0;
    }
    
    return this->doRecoverContacts(listener);
}


int FunambolAPIWrapperImpl::doRecoverContacts(id listener) {
    if (!loadClientConfig()) {
        return 0;
    }
    
    SyncMode oldMode = getAddressBookSyncSource()->getSyncMode();
    getAddressBookSyncSource()->setSyncMode(recoverDirection);
    int ret = this->doSync(listener, "addressbook");
    getAddressBookSyncSource()->setSyncMode(oldMode);
    
    return ret;
}


int FunambolAPIWrapperImpl::sync(id listener, const char* sourcename) {
    if (!loadClientConfig(true)) {
    //if (!loadClientConfig()) {
        return 0;
    }

    return this->doSync(listener, sourcename);
}
#ifndef FUNAMBOL_APP_STORE
NoteSyncSource* FunambolAPIWrapperImpl::getNoteSyncSource(){
    if (!loadClientConfig()) {
        return NULL;
    }
    if(noteIsSyncing){
        return nss;
    }
    if(nss){
        delete nss;
        nss = NULL;
    }
        
    NoteDataStore* nds = new NoteDataStore(NOTEDBPATH);
    NoteChangesTracker* nct = new NoteChangesTracker(NOTEDBPATH);
    NoteDataConverter* ndc = new NoteDataConverter();
    clientConfig->read();
    SyncSourceConfig* nsc = clientConfig->getSyncSourceConfig("note", true);
    LOG.debug("FAWI: getNoteSyncSource %s", nsc->getName());
    
    nss = new NoteSyncSource((*nsc), (PIMDataStore&)*nds, (PIMDataConverter&)*ndc, (ChangesTracker&)*   nct);   
        
    return nss;
}

EventSyncSource* FunambolAPIWrapperImpl::getEventSyncSource(){
    if (!loadClientConfig()) {
        return NULL;
    }
    if(calendarIsSyncing){
        return ess;
    }
    if(ess){
        delete ess;
        ess = NULL;
    }
    
    EventDataStore* eds = new EventDataStore(EVENTDBPATH);
    EventChangesTracker* ect = new EventChangesTracker(EVENTDBPATH);
    EventDataConverter* edc = new EventDataConverter();
    clientConfig->read();
    SyncSourceConfig* esc = clientConfig->getSyncSourceConfig("calendar", true);
    LOG.debug("FAWI: getEventSyncSource %s", esc->getName());
    
    ess = new EventSyncSource((*esc), (PIMDataStore&)*eds, (PIMDataConverter&)*edc, (ChangesTracker&)*   ect);
    
    
    return ess;
}
#endif

AddressBookSyncSource* FunambolAPIWrapperImpl::getAddressBookSyncSource(){
    if (!loadClientConfig()) {
        return NULL;
    }
    if(!abss){
        SyncSourceConfig *sc = clientConfig->getSyncSourceConfig("addressbook", true);
        if (!sc) {
            LOG.error("[SyncThread] Sync source for addressbook not found (%d)",
                      clientConfig->getNumSources());
            //return NO_SOURCE_CONFIGURED_ERR;
            return NULL;
        }
        
        abss = new AddressBookSyncSource(sc->getName(), sc);
    }
    return abss;
}

int FunambolAPIWrapperImpl::doSync(id listener, const char* syncSourceName) {
    if (!loadClientConfig()) {
        return 0;
    }
    
    int res = 1;
    
    iPhoneSyncItemListener itemListener(listener, syncSourceName);
    iPhoneSyncSourceListener sourceListener(listener, syncSourceName);
    if (listener != NULL) {
        ManageListener::getInstance().setSyncItemListener(&itemListener);
        ManageListener::getInstance().setSyncSourceListener(&sourceListener);
    }
    

    // remember for use by sync sources
    StringBuffer url = clientConfig->getAccessConfig().getSyncURL() ?
    clientConfig->getAccessConfig().getSyncURL() : "";
    if (!url.length()) {
        LOG.error("[SyncThread] Cannot proceed without configuration");
        return URL_NOT_CONFIGURED_ERR;
    }
    
    // dump some summary information at the beginning of the log
    LOG.debug("[SyncThread] client: Funambol iPhone Plugin");
    time_t now = time(NULL);
    LOG.info("[SyncThread] current UTC date and time: %s",asctime(gmtime(&now)));
    LOG.info("[SyncThread] SyncML server account: %s", clientConfig->getAccessConfig().getUsername());
    
    // do it
    SyncSource* sourcesList[2];

    if (strcmp(syncSourceName, "addressbook") == 0){

        sourcesList[0] = getAddressBookSyncSource();
        contactIsSyncing = true;
        calendarIsSyncing = false;
        noteIsSyncing = false;
    
    }
#ifndef FUNAMBOL_APP_STORE
    else if (strcmp(syncSourceName, "calendar") == 0){

        sourcesList[0] = getEventSyncSource();
        contactIsSyncing = false;
        calendarIsSyncing = true;
        noteIsSyncing = false;
    
    }else if (strcmp(syncSourceName, "note") == 0){
        
        sourcesList[0] = getNoteSyncSource();
        contactIsSyncing = false;
        calendarIsSyncing = false;
        noteIsSyncing = true;
    }
#endif
        
    sourcesList[1] = NULL;
    SyncClient syncClient;
    LOG.debug("About to start sync");
    res = syncClient.sync(*clientConfig, sourcesList);
    LOG.debug("Sync done");
    
    if (listener != NULL) {
        ManageListener::getInstance().unsetSyncItemListener();
        ManageListener::getInstance().unsetSyncSourceListener();
    }
    contactIsSyncing = false;
    calendarIsSyncing = false;
    noteIsSyncing = false;
    // store modified properties: must be done even after failed
    // sync because the source's anchor might have been reset
    clientConfig->save();
    
    return res;
}

void FunambolAPIWrapperImpl::resetSyncDate(const char* sourceName){
    if (!loadClientConfig()) {
    }
    
    // Get the source configuration
    SyncSourceConfig *sc = clientConfig->getSyncSourceConfig(sourceName);
    if (sc == NULL) {
        LOG.error("[SyncThread] Sync source for addressbook not found (%d)",
                  clientConfig->getNumSources());
    } else {
        sc->setLast(0);
        clientConfig->save();
        loadClientConfig(TRUE);
    }
}

int FunambolAPIWrapperImpl::getLastSyncDate(const char* sourceName) {
    
    if (!loadClientConfig()) {
        return 0;
    }
    
    // Get the source configuration
    SyncSourceConfig *sc = clientConfig->getSyncSourceConfig(sourceName);
    if (sc == NULL) {
        LOG.error("[SyncThread] Sync source for addressbook not found (%d)",
                  clientConfig->getNumSources());
        return 0;
    } else {
        int res = sc->getLast();
        return res;;
    }
}

const char* FunambolAPIWrapperImpl::getSync(const char* sourceName) {
    if (!loadClientConfig()) {
        return 0;
    }
    
    // Get the source configuration
    SyncSourceConfig *sc = clientConfig->getSyncSourceConfig(sourceName);
    if (sc == NULL) {
        LOG.error("[SyncThread] Sync source for addressbook not found (%d)",
                  clientConfig->getNumSources());
        return 0;
    } else {
        
        //return sc->getSyncModes();
        return sc->getSync();
    }
}   

void FunambolAPIWrapperImpl::setSync(const char* sourceName, const char* syncModes){
    if (!loadClientConfig()) {
        
        exit(-1);
    }
    
    // Get the source configuration
    SyncSourceConfig *sc = clientConfig->getSyncSourceConfig(sourceName);
    if (sc == NULL) {
        LOG.error("[SyncThread] Sync source for addressbook not found (%d)",
                  clientConfig->getNumSources());
        exit(-1);
    } else {
        //sc->setSyncModes(syncModes);
        sc->setSync(syncModes);
        clientConfig->save();
    }
}   

const char* FunambolAPIWrapperImpl::getURI(const char* sourceName) {
    if (!loadClientConfig()) {
        return 0;
    }
    
    // Get the source configuration
    SyncSourceConfig *sc = clientConfig->getSyncSourceConfig(sourceName);
    if (sc == NULL) {
        LOG.error("[SyncThread] Sync source for addressbook not found (%d)",
                  clientConfig->getNumSources());
        return 0;
    } else {
        
        return sc->getURI();
    }
}   

void FunambolAPIWrapperImpl::setURI(const char* sourceName, const char* URI){
    if (!loadClientConfig()) {
        exit(-1);
    }
    
    // Get the source configuration
    SyncSourceConfig *sc = clientConfig->getSyncSourceConfig(sourceName);
    if (sc == NULL) {
        LOG.error("[SyncThread] Sync source for addressbook not found (%d)",
                  clientConfig->getNumSources());
        exit(-1);
    } else {
        sc->setURI(URI);
        clientConfig->save();
    }
}  



void FunambolAPIWrapperImpl::saveSyncConfig(const char* url, const char* username,
                                            const char* password) {
    
    if (!loadClientConfig()) {
        return;
    }
    if (url != NULL) {
        clientConfig->getAccessConfig().setSyncURL(url);
    }
    
    if (username != NULL) {
        clientConfig->getAccessConfig().setUsername(username);
    }
    
    if (password != NULL) {
        clientConfig->getAccessConfig().setPassword(password);
    }
    clientConfig->save();
}

void FunambolAPIWrapperImpl::saveURL(const char* url){
    if (!loadClientConfig()) {
        return;
    }
    if (url != NULL) {
        clientConfig->getAccessConfig().setSyncURL(url);
    }
    clientConfig->save();
}

void FunambolAPIWrapperImpl::saveUsername(const char* username){
    if (!loadClientConfig()) {
        return;
    }
    if (username != NULL) {
        clientConfig->getAccessConfig().setUsername(username);
    }
    clientConfig->save();
}

void FunambolAPIWrapperImpl::savePassword(const char* password){
    if (!loadClientConfig()) {
        return;
    }
    if (password != NULL) {
        clientConfig->getAccessConfig().setPassword(password);
    }
    clientConfig->save();
}

const char* FunambolAPIWrapperImpl::loadUserNameFromSyncConfig() {
    LOG.debug("loadUserNameFromSyncConfig");
    if (!loadClientConfig()) {
        return NULL;
    }
    return clientConfig->getAccessConfig().getUsername();
}

const char* FunambolAPIWrapperImpl::loadPasswordFromSyncConfig() {
    if (!loadClientConfig()) {
        return NULL;
    }
    return clientConfig->getAccessConfig().getPassword();
}

const char* FunambolAPIWrapperImpl::loadUrlFromSyncConfig() {
    if (!loadClientConfig()) {
        return NULL;
    }
    return clientConfig->getAccessConfig().getSyncURL();
}

void FunambolAPIWrapperImpl::setLogLevel(int loglevel){
    switch (loglevel) {
        case 0:
            clientConfig->getDeviceConfig().setLogLevel(LOG_LEVEL_NONE);
            clientConfig->save();
            LOG.setLevel(LOG_LEVEL_NONE);
            break;
        case 1:
            clientConfig->getDeviceConfig().setLogLevel(LOG_LEVEL_INFO);
            clientConfig->save();
            LOG.setLevel(LOG_LEVEL_INFO);
            break;
        case 2:
            clientConfig->getDeviceConfig().setLogLevel(LOG_LEVEL_DEBUG);
            clientConfig->save();
            LOG.setLevel(LOG_LEVEL_DEBUG);
            break;
        default:
            break;
    }
}



